/**
 * 
 */
package com.ubo.xml.analyser;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;

import com.ubo.java.associations.Association;
import com.ubo.java.associations.EAssociationType;
import com.ubo.java.model.EModifier;
import com.ubo.java.model.JavaModel;
import com.ubo.java.model.PackageModel;
import com.ubo.java.model.classifier.ClassModel;
import com.ubo.java.model.classifier.IClassifier;
import com.ubo.java.model.classifier.Interface;
import com.ubo.java.model.declaration.Attribut;
import com.ubo.java.model.declaration.Parameter;
import com.ubo.java.model.methode.Method;
import com.ubo.java.model.methode.Prototype;
import com.ubo.transform.AAnalyser;

/**
 * @author maximev
 * 
 */
public class DTDParser extends AAnalyser {
	/** Chemin de la dtd. */
	protected String mFilePath;

	/** Element root du XML. */
	protected Element mElementRoot;

	/** Reader. */
	protected SAXBuilder mReader;

	/** Document DOM. */
	protected Document mDocument;

	/** Fichier XML. */
	protected File mFile;
	protected Element currentE;
	protected PackageModel currentP;
	protected PackageModel pm;
	protected JavaModel jm;

	/**
	 * Pile d'element
	 */
	protected Stack<Method> mStackMethod;
	protected Stack<Prototype> mStackPrototype;
	protected Stack<Parameter> mStackParameter;
	protected Stack<Attribut> mStackAttribut;
	protected Stack<Association> mStackAssociation;
	protected Map<String, IClassifier> mappingClass;

	/**
	 * 
	 * Constructor
	 * 
	 * @param filePath
	 */
	public DTDParser(String filePath) {
		mFilePath = filePath;
		init();
	}

	/**
	 * Initialise les fichiers.
	 */
	protected void init() {
		mReader = new SAXBuilder();
		mElementRoot = new Element("element");
		mFile = new File(mFilePath);
		try {
			mDocument = mReader.build(mFile);
		} catch (JDOMException | IOException e) {
			mDocument = new Document(mElementRoot);
		}
		mElementRoot = mDocument.getRootElement();
		mStackMethod = new Stack<Method>();
		mStackPrototype = new Stack<Prototype>();
		mStackParameter = new Stack<Parameter>();
		mStackAttribut = new Stack<Attribut>();
		mStackAssociation = new Stack<Association>();
		mappingClass = new HashMap<String, IClassifier>();
	}

	/**
	 * 
	 * Génération du JavaModel
	 */
	public void generateJavaModel() {
		pm = new PackageModel("src");
		jm = new JavaModel(pm);
		currentP = pm;
		for (int i = 0; i < mElementRoot.getChildren().size(); i++) {
			currentE = mElementRoot.getChildren().get(i);
			if (currentE.getName().compareTo("Package") == 0) {
				generatePackage(currentE);
			}
		}
		Set<IClassifier> ac = new HashSet<IClassifier>();
		for (IClassifier c : mappingClass.values()) {
			ac.add(c);
		}
		jm.addClassifiers(ac);
		for (int i = 0; i < mElementRoot.getChildren().size(); i++) {
			currentE = mElementRoot.getChildren().get(i);
			if (currentE.getName().toString().compareTo("Association") == 0) {
				generateAssociation(currentE);
			}
		}
	}

	/**
	 * Génération d'un package Gestion Class / Interface / Package
	 * 
	 * @param ePack
	 */
	protected void generatePackage(Element ePack) {
		PackageModel saveP = currentP;
		Element saveE = currentE;
		PackageModel pack = new PackageModel("."
				+ ePack.getAttributeValue("name").toString());
		pack.setmFatherPackage(currentP);
		currentE = ePack;
		currentP = pack;
		for (int i = 0; i < currentE.getChildren().size(); i++) {
			Element child = currentE.getChildren().get(i);
			if (child.getName().toString().compareTo("Classifier") == 0) {
				if (child.getAttributeValue("type").toString()
						.compareTo("interface") == 0) {
					generateInterface(child);
				} else if (child.getAttributeValue("type").toString()
						.compareTo("class") == 0) {
					generateClass(child);
				}
			}
			if (child.getName().toString().compareTo("Package") == 0) {
				generatePackage(child);
			}
		}
		currentE = saveE;
		currentP = saveP;
	}

	/**
	 * Génération d'une class Gestion Attribut / Method / Constructeur
	 * 
	 * 
	 * @param eClass
	 */
	protected void generateClass(Element eClass) {
		Element saveE = currentE;
		ClassModel cm = new ClassModel(eClass.getAttributeValue("name")
				.toString(), currentP, elementVisibility(eClass));
		currentE = eClass;
		for (int i = 0; i < eClass.getChildren().size(); i++) {
			Element child = currentE.getChildren().get(i);
			if (child.getName().toString().compareTo("Attribute") == 0) {
				generateAttribute(child);
			} else if (child.getName().toString().compareTo("Methode") == 0) {
				if (child.getAttributeValue("visibility").toString()
						.compareTo("abstract") == 0)
					generatePrototype(child);
				else
					generateMethod(child);

			}
		}
		// Récupération des attributs
		Set<Attribut> sA = new HashSet<Attribut>();
		for (int i = mStackAttribut.size(); i > 0; i--) {
			sA.add(mStackAttribut.pop());
		}
		cm.addAllAttr(sA);
		// Récupération des méthodes
		Set<Method> sM = new HashSet<Method>();
		for (int i = mStackMethod.size(); i > 0; i--) {
			sM.add(mStackMethod.pop());
		}
		cm.addAllMeth(sM);
		// Récupération des prototypes
		Set<Prototype> sP = new HashSet<Prototype>();
		for (int i = mStackPrototype.size(); i > 0; i--) {
			sP.add(mStackPrototype.pop());
		}
		cm.addAllProto(sP);
		currentE = saveE;
		mappingClass.put(cm.getClassifierName(), cm);
	}

	/**
	 * Génération d'une interface
	 * 
	 * @param eInt
	 */
	protected void generateInterface(Element eInter) {
		Element saveE = currentE;
		Interface inter = new Interface(eInter.getAttributeValue("name")
				.toString(), currentP, elementVisibility(eInter));
		currentE = eInter;
		for (int i = 0; i < eInter.getChildren().size(); i++) {
			Element child = currentE.getChildren().get(i);
			if (child.getName().toString().compareTo("Attribute") == 0) {
				generateAttribute(child);
			} else if (child.getName().toString().compareTo("Methode") == 0) {
				generatePrototype(child);
			}
		}
		// Récupération des attributs
		Set<Attribut> sA = new HashSet<Attribut>();
		for (int i = mStackAttribut.size(); i > 0; i--) {
			sA.add(mStackAttribut.pop());
		}
		inter.addAllAttr(sA);

		// Récupération des prototypes
		Set<Prototype> cP = new HashSet<Prototype>();
		for (int i = mStackPrototype.size(); i > 0; i--) {

			cP.add(mStackPrototype.pop());
		}
		inter.addAllPrototypes(cP);
		currentE = saveE;
		mappingClass.put(inter.getClassifierName(), inter);

	}

	/**
	 * Génération de méthode
	 * 
	 * @param eMeth
	 */
	protected void generateMethod(Element eMeth) {
		Element saveE = currentE;
		int saveStackCpt = mStackParameter.size();
		Set<EModifier> em = null;
		if (elementModifier(eMeth) != null) {
			em = new HashSet<EModifier>();
			em.add(elementModifier(eMeth));
		}

		currentE = eMeth;
		for (int i = 0; i < eMeth.getChildren().size(); i++) {
			Element child = currentE.getChildren().get(i);
			if (child.getName().toString().compareTo("Attribute") == 0) {
				generateParameter(child);
			}
		}
		// Récupération des attributs
		Set<Parameter> ap = new HashSet<Parameter>();
		for (int i = mStackParameter.size(); i > saveStackCpt; i--) {

			ap.add((mStackParameter.pop()));
		}
		Method m = new Method(eMeth.getAttributeValue("name").toString(), em,
				elementVisibility(eMeth), null, ap,
				elementPrimitiveType(eMeth), false);
		currentE = saveE;
		mStackMethod.push(m);
	}

	/**
	 * Génération de methode prototype
	 * 
	 * @param eProto
	 */
	protected void generatePrototype(Element eProto) {
		Element saveE = currentE;
		int saveStackCpt = mStackParameter.size();
		Set<EModifier> em = null;
		if (elementModifier(eProto) != null) {
			em = new HashSet<EModifier>();
			em.add(elementModifier(eProto));
		}
		currentE = eProto;

		for (int i = 0; i < eProto.getChildren().size(); i++) {
			Element child = currentE.getChildren().get(i);

			if (child.getName().toString().compareTo("Attribute") == 0) {
				generateParameter(child);
			}
		}
		// Récupération des attributs
		Set<Parameter> ap = new HashSet<Parameter>();
		for (int i = mStackParameter.size(); i > saveStackCpt; i--) {

			ap.add((mStackParameter.pop()));
		}
		Prototype p = new Prototype(
				eProto.getAttributeValue("name").toString(), em,
				elementVisibility(eProto), null, ap,
				elementPrimitiveType(eProto));
		currentE = saveE;
		mStackPrototype.push(p);
	}

	/**
	 * Génération d'un attribut
	 * 
	 * @param eAttrib
	 */
	protected void generateAttribute(Element eAttrib) {

		ArrayList<EModifier> em = null;
		if (elementModifier(eAttrib) != null) {
			em = new ArrayList<EModifier>();
			em.add(elementModifier(eAttrib));
		}

		Attribut a = new Attribut(eAttrib.getAttributeValue("name").toString(),
				elementPrimitiveType(eAttrib), em, elementVisibility(eAttrib));
		mStackAttribut.push(a);
	}

	/**
	 * Génération d'une parametre
	 * 
	 * @param aParam
	 */
	protected void generateParameter(Element aParam) {

		ArrayList<EModifier> em = null;
		if (elementModifier(aParam) != null) {
			em = new ArrayList<EModifier>();
			em.add(elementModifier(aParam));
		}

		Parameter p = new Parameter(
				aParam.getAttributeValue("name").toString(),
				elementPrimitiveType(aParam), em);
		mStackParameter.push(p);
	}

	/**
	 * Génération d'une Association
	 * 
	 * @param eAssoc
	 */
	protected void generateAssociation(Element eAssoc) {
		IClassifier iSource = mappingClass.get(eAssoc.getAttributeValue(
				"source").toString());
		IClassifier iDest = mappingClass.get(eAssoc.getAttributeValue(
				"destination").toString());
		if (iSource != null && iDest != null) {
			if (elementAssociationType(eAssoc).equals(
					EAssociationType.IMPLEMENTS)) {
				mappingClass.get(eAssoc.getAttributeValue("source").toString())
						.implementsClassifier(
								mappingClass.get(eAssoc.getAttributeValue(
										"destination").toString()));
			} else if (elementAssociationType(eAssoc).equals(
					EAssociationType.EXTENDS)) {
				mappingClass.get(eAssoc.getAttributeValue("source").toString())
						.extendsClassifier(
								mappingClass.get(eAssoc.getAttributeValue(
										"destination").toString()));

			} else {
				jm.addAssociation(iSource, iDest,
						elementAssociationType(eAssoc),
						elementCard(eAssoc, "cardSource"),
						elementCard(eAssoc, "cardDest"));
			}
		}
	}

	/**
	 * Récupération du JavaModel
	 * 
	 * @return
	 */
	public JavaModel getJm() {
		return jm;
	}
}
